package hivMultiCompartment;

import hivMultiCompartment.TCell.AgentState;

import java.awt.Point;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;

public abstract class Cell extends MobileObject {
	
	public ArrayList<Cell> myCellList;
	public int proliferateCD;
	public int proliferateInterval;
	public boolean limitedPorliferation; //true if proliferation is limited(activated/etc), false if not (memory/naive...)
	public int proliferateCount;
	//infection related
	public int presentStrain;
	public int recognizeStrain;
	
	public Cell() {
		super ();
		proliferateCD = 0;
		proliferateInterval = 0;
		proliferateCount = 0;
		limitedPorliferation = true;
		presentStrain = 0;
		recognizeStrain = 0;
	}
	
	/**
	 * when recruited, imported or created.
	 */
	public void updateMyList(ArrayList<Cell> list){
		myCellList = list;
	}
	/**
	 * when exiting an organ.
	 */
	public void clearMyList(){
		myCellList = null;
	}
	
	/**
	 * handles proliferations in step()
	 */
	public boolean step () {
		boolean toExit = super.step();
			
		boolean toProliferate = false;
		Point OpenPt = null;
		
		if (proliferateCount > 0) {
			if(proliferateCD == 0) {
				if (Organ2D != null) {
					OpenPt = Organ2D.cellSpace.getRandomOpenNeighborLocation(x, y, 2);
					if (OpenPt != null){ // found open spot
						toProliferate = true;
					}
				}
				else {
					toProliferate = true;
				}
			}
			else {
				--proliferateCD;
			}
		}
		//if a cell already exited, it won't proliferate (organ2D/organNS has changed)
		if(toExit) {
			return toExit;
		}
		else if (toProliferate) {
			if(limitedPorliferation) {
				--proliferateCount;
			}
			Cell c = cellProliferate(this);
			//System.out.println(c.toString());
			//if in Organ2D, put on grid
			if(Organ2D != null) {
				Organ2D.prolifCellList.add(c);
				Organ2D.cellSpace.addObjectAt((int)OpenPt.getX(), (int)OpenPt.getY(), c);
			}else {
				OrganNS.prolifCellList.add(c);
			}
			resetProliferateIntervalAndavgLife();
			c.resetProliferateIntervalAndavgLife();
		}
		return toExit;
	}
	
	/**
	 * reset proliferation interval and life max for daughter cells
	 */
	public void resetProliferateIntervalAndavgLife(){
//		proliferateInterval = (int)Model.getNormalDouble(proliferateInterval, proliferateInterval/20.0);
//		if(proliferateInterval <= 0) {
//			proliferateInterval = 1;
//		}
		//renew lifespan
		life =  GlobalConstants.sampleNormal(avgLife);
		//reset proliferateCD
		proliferateCD = proliferateInterval - 1;
	}
	
	public Cell cellProliferate (Cell c){
		try {
			Cell nc = (Cell)c.clone();
			return nc;
		} catch (CloneNotSupportedException e) {
			System.out.println("Cloning not working!");
			e.printStackTrace();
			return null;
		}
		
		
	}
	
	/**
	 * search neighborhood for matching CD8 effectors;
	 * return the probability of been killed
	 * @return
	 */
	public double probKilledByMatchingCD8TEffector(){
		double probN = 1; //probability of not been killed
		ArrayList<Object> cList = Organ2D.cellSpace.getMooreNeighborOfType(x, y, 1, CD8TCell.class);
		Iterator<Object> cIter = cList.iterator();
		while(cIter.hasNext()) {
			CD8TCell c = (CD8TCell)cIter.next();
			if(c.isAlive() && c.isEffector() && c.recognizeStrain == presentStrain) {
				probN *= (1-c.killingEfficiency);
			}
		}
//		if(probN < 0.99){
//			System.out.println("probN: " + probN);
//		}
		return (1 - probN);
		
	}
	
	/**
	 * infected cells will activate matching CD8 Memory cells in the neighborhood
	 */
	public void activateMatchingCD8Memory(){
		ArrayList<Object> cList = Organ2D.cellSpace.getMooreNeighborOfType(x, y, 1, CD8TCell.class);
		Iterator<Object> cIter = cList.iterator();
		while(cIter.hasNext()) {
			CD8TCell c = (CD8TCell)cIter.next();
			if(c.isAlive() && c.isMemory() && c.recognizeStrain == presentStrain) {
				c.changeState(AgentState.ACTIVE);
			}
		}
	}
	
	/**
	 * calculate infection probability; 
	 * roll a dice to see if get infected, if yes, return a random virus.
	 * @return
	 */
	public Virus getALocalVirus(){
		Virus v = null;
		HashSet<Virus> vList = (HashSet<Virus>)Organ2D.virusSpace.getObjectAt(x, y);
		int cVirus = vList.size();
		double probInfect = 1- Math.pow((1 - GlobalConstants.probEnterCell), cVirus);
		
		double r = Model.getUniformDoubleFromTo(0, 1);
		if(r < probInfect) {
			int n = Model.getUniformIntFromTo(0, cVirus -1 );
			ArrayList<Virus> vListTemp = new ArrayList<Virus>();
			vListTemp.addAll(vList);
			v = vListTemp.get(n);
			//System.out.println("Infected!");
		}
		return v;
	}
	
	public boolean processLife(){
		//die
		--life;
		if (life < 0) {
			agentDie();
			return false;
		}
		return true;
	}
	
	public void agentDie() {
		super.agentDie();
		if(Organ2D != null) {
			Organ2D.cellSpace.removeObjectFromGrid(this);
		}
		alive = false;
	}
}
